This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.
Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter. Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I. When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).
The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.
========================================================
Course: CEBD1261 Big Data Infrstructure
By: Luciana Palucci
========================================================
# Define library packages to use and pre-install
# These packages must be installed at first if not already installed.
#install.packages("tidyverse", dependencies=TRUE)
#install.packages("plotly")
#install.packages("RCurl")
library(dplyr,verbose = getOption("verbose"))
library(tidyr) # for reshaping data
package <U+393C><U+3E31>tidyr<U+393C><U+3E32> was built under R version 3.4.4
Attaching package: <U+393C><U+3E31>tidyr<U+393C><U+3E32>
The following object is masked from <U+393C><U+3E31>package:RCurl<U+393C><U+3E32>:
complete
library(scales) # for scale_y_continuous(label = percent)
library(ggthemes) # for scale_fill_few('medium')
package <U+393C><U+3E31>ggthemes<U+393C><U+3E32> was built under R version 3.4.4
library(ggplot2) # plotting data
library(ztable) # format tables for reporting
Welcome to package ztable ver 0.1.8
library(tidyverse)
package <U+393C><U+3E31>tidyverse<U+393C><U+3E32> was built under R version 3.4.4[30m-- [1mAttaching packages[22m --------------------------------------- tidyverse 1.2.1 --[39m
[30m[32mv[30m [34mtibble [30m 1.4.2 [32mv[30m [34mpurrr [30m 0.2.4
[32mv[30m [34mreadr [30m 1.1.1 [32mv[30m [34mstringr[30m 1.2.0
[32mv[30m [34mtibble [30m 1.4.2 [32mv[30m [34mforcats[30m 0.3.0[39m
package <U+393C><U+3E31>tibble<U+393C><U+3E32> was built under R version 3.4.4package <U+393C><U+3E31>readr<U+393C><U+3E32> was built under R version 3.4.4package <U+393C><U+3E31>purrr<U+393C><U+3E32> was built under R version 3.4.4package <U+393C><U+3E31>forcats<U+393C><U+3E32> was built under R version 3.4.4[30m-- [1mConflicts[22m ------------------------------------------ tidyverse_conflicts() --
[31mx[30m [34mreadr[30m::[32mcol_factor()[30m masks [34mscales[30m::col_factor()
[31mx[30m [34mtidyr[30m::[32mcomplete()[30m masks [34mRCurl[30m::complete()
[31mx[30m [34mpurrr[30m::[32mdiscard()[30m masks [34mscales[30m::discard()
[31mx[30m [34mdplyr[30m::[32mfilter()[30m masks [34mstats[30m::filter()
[31mx[30m [34mpurrr[30m::[32minvoke()[30m masks [34msparklyr[30m::invoke()
[31mx[30m [34mdplyr[30m::[32mlag()[30m masks [34mstats[30m::lag()[39m
library(plotly) # for interactive charts
package <U+393C><U+3E31>plotly<U+393C><U+3E32> was built under R version 3.4.4
Attaching package: <U+393C><U+3E31>plotly<U+393C><U+3E32>
The following object is masked from <U+393C><U+3E31>package:ggplot2<U+393C><U+3E32>:
last_plot
The following object is masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:
filter
The following object is masked from <U+393C><U+3E31>package:graphics<U+393C><U+3E32>:
layout
Read in Bureau of Transport Statistics dataset from
#Pipeline the data into Rstudio
#Get data from AWS to local copy of rstudio
#Source: https://aws.amazon.com/blogs/big-data/running-r-on-aws/
#library("RCurl")
#data <- read.table(getURL("https://ec2-54-237-132-222.compute-1.amazonaws.com/Data/On_Time_Performance_2018.csv"), sep=",", #header=FALSE)
#head(data)
#read in the raw dataset
airports <- read.csv("C:/Users/Luciana/CEBD1261/CEBD1261_Project/Data/On_Time_Performance_2018.csv", header = TRUE)
# view summary for unusual data values
summary(airports$DepTime)
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
1 922 1329 1334 1738 2400 16826
Clean the dataset to exclude null values and time format that do not conform to standard, then add variable
to break time point into 15 min interval timepoint.
# Remove the rows by checking if not (!) an NA due to not meeting the date format
temp<- airports[!is.na(strptime(airports$DepTime, format = "%H%M")),]
class(temp$DepTime)
[1] "integer"
#Split time series data into time intervals by every 15 minutes and then plot the count
myDF<- mutate(temp, byGroup = as.matrix(cut(strptime(sub("(\\d+)(\\d{2})", "\\1:\\2", temp$DepTime), format = "%H:%M"), breaks="15 min") ))
# unique(myDF$byGroup) #check unique values
# Extract time value from variable and make dataframes used for graphs and summary table
myDF["Time"] <- NA # This creates the new column filled with "NA"
myDF$Time <- strftime(as.POSIXlt(myDF$byGroup), format="%H:%M")
1. Big Data Example
===================================
Make dataframes used for graphs and summary table
# based on variable values
ds1 <- myDF[ which(myDF$DayofMonth == 1 & myDF$OriginState=="NY"), ]
ds1[with(ds1, order(Time)), ]
Plot the data using ggplot2 and pipes
# Prepare dataset and pipe the dataset into the graph
graph1 <- ds1 %>%
ggplot(aes(x = Time, y = Origin, group=OriginStateName, color = Carrier)) +
geom_point() + scale_x_discrete(breaks=c("00:00", "11:00", "12:00", "14:00","16:00","18:00", "20:00","23:00") )
labs(title = "New York State Airport Departures by Time Intervals",
subtitle = "*The data frame is sent to the plot using pipes",
y = "Airport Origin code",
x = "Departure Time") + theme_bw(base_size = 8) +
theme(axis.ticks = element_blank(), axis.text.x = element_blank())
NULL
#printout graph #1
graph1

Turn the gplot into an interactive chart.
ggplotly(graph1)
We recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`
2. Sampling and BI Summary Example
===================================
Count Distribution by State and Origin City of Airport Departures by Carrier
# Create dataset with overall count distributions
ds2 <- myDF[ which(myDF$Carrier %in% c("9E","AA","AS","B6","DL","EV","F9")), ]
dfr_prop <- ds2 %>%
count(OriginState, Carrier) %>% # group_by() & summarise(n=n()) are implicit
mutate(prop = prop.table(n)) # prop = n/sum(n) works too / prop.table(n)
as.data.frame(dfr_prop) # print df
#Plot distribution percentage summary for month of January
gg_prop <- ggplot(data = data.frame()
, aes(x =OriginState, y = prop, fill = Carrier)) +
geom_bar(stat = 'identity', position = 'stack', alpha = 2/3) +
scale_y_continuous(labels = percent) +
scale_x_discrete(breaks=c("AK", "CA","FL", "IL", "GA","LA","MI","MO","NC","OH", "NJ","NY","PA","TX","WY") ) +
scale_fill_few('medium', drop = FALSE) + # keep levels, if data is filtered
labs(x = 'State', y = NULL, fill = 'Carrier',
title = 'Distribution of Flights by State and Carrier for Month of January') # title of graph
gg_prop %+% dfr_prop

End of program.
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiANCg0KVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqLiBBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSouDQpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4NCg0KVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLg0KDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiNDb3Vyc2U6IENFQkQxMjYxIEJpZyBEYXRhIEluZnJzdHJ1Y3R1cmUNCiNCeTogICAgIEx1Y2lhbmEgUGFsdWNjaQ0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCg0KYGBge3J9DQojIERlZmluZSBsaWJyYXJ5IHBhY2thZ2VzIHRvIHVzZSBhbmQgcHJlLWluc3RhbGwNCg0KIyBUaGVzZSBwYWNrYWdlcyBtdXN0IGJlIGluc3RhbGxlZCBhdCBmaXJzdCBpZiBub3QgYWxyZWFkeSBpbnN0YWxsZWQuDQoNCiNpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiLCBkZXBlbmRlbmNpZXM9VFJVRSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKQ0KI2luc3RhbGwucGFja2FnZXMoIlJDdXJsIikNCiAgDQpsaWJyYXJ5KGRwbHlyLHZlcmJvc2UgPSBnZXRPcHRpb24oInZlcmJvc2UiKSkNCmxpYnJhcnkodGlkeXIpICAgICMgZm9yIHJlc2hhcGluZyBkYXRhDQpsaWJyYXJ5KHNjYWxlcykgICAgICMgZm9yIHNjYWxlX3lfY29udGludW91cyhsYWJlbCA9IHBlcmNlbnQpDQpsaWJyYXJ5KGdndGhlbWVzKSAgICMgZm9yIHNjYWxlX2ZpbGxfZmV3KCdtZWRpdW0nKQ0KbGlicmFyeShnZ3Bsb3QyKSAgICAjIHBsb3R0aW5nIGRhdGENCmxpYnJhcnkoenRhYmxlKSAgICAgIyBmb3JtYXQgdGFibGVzIGZvciByZXBvcnRpbmcNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwbG90bHkpICAgICAjIGZvciBpbnRlcmFjdGl2ZSBjaGFydHMNCiANCg0KYGBgDQojIFJlYWQgaW4gQnVyZWF1IG9mIFRyYW5zcG9ydCBTdGF0aXN0aWNzIGRhdGFzZXQgZnJvbQ0KIyBodHRwczovL3d3dy50cmFuc3RhdHMuYnRzLmdvdi9ETF9TZWxlY3RGaWVsZHMuYXNwP1RhYmxlX0lEPTIzNiZEQl9TaG9ydF9OYW1lPU9uLVRpbWUNCg0KYGBge3J9DQojUGlwZWxpbmUgdGhlIGRhdGEgaW50byBSc3R1ZGlvDQoNCiNHZXQgZGF0YSBmcm9tIEFXUyB0byBsb2NhbCBjb3B5IG9mIHJzdHVkaW8NCiNTb3VyY2U6IGh0dHBzOi8vYXdzLmFtYXpvbi5jb20vYmxvZ3MvYmlnLWRhdGEvcnVubmluZy1yLW9uLWF3cy8NCg0KI2xpYnJhcnkoIlJDdXJsIikgDQojZGF0YSA8LSByZWFkLnRhYmxlKGdldFVSTCgiaHR0cHM6Ly9lYzItNTQtMjM3LTEzMi0yMjIuY29tcHV0ZS0xLmFtYXpvbmF3cy5jb20vRGF0YS9Pbl9UaW1lX1BlcmZvcm1hbmNlXzIwMTguY3N2IiksIHNlcD0iLCIsICNoZWFkZXI9RkFMU0UpDQojaGVhZChkYXRhKQ0KDQpgYGANCg0KYGBge3J9DQoNCiNyZWFkIGluIHRoZSByYXcgZGF0YXNldA0KYWlycG9ydHMgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL0x1Y2lhbmEvQ0VCRDEyNjEvQ0VCRDEyNjFfUHJvamVjdC9EYXRhL09uX1RpbWVfUGVyZm9ybWFuY2VfMjAxOC5jc3YiLCBoZWFkZXIgPSBUUlVFKQ0KDQojIHZpZXcgc3VtbWFyeSBmb3IgdW51c3VhbCBkYXRhIHZhbHVlcw0Kc3VtbWFyeShhaXJwb3J0cyREZXBUaW1lKQ0KDQpgYGANCg0KIw0KIyBDbGVhbiB0aGUgZGF0YXNldCB0byBleGNsdWRlIG51bGwgdmFsdWVzIGFuZCB0aW1lIGZvcm1hdCB0aGF0IGRvIG5vdCBjb25mb3JtIHRvIHN0YW5kYXJkLCB0aGVuIGFkZCB2YXJpYWJsZQ0KIyB0byBicmVhayB0aW1lIHBvaW50IGludG8gMTUgbWluIGludGVydmFsIHRpbWVwb2ludC4NCiMNCmBgYHtyfQ0KIyBSZW1vdmUgdGhlIHJvd3MgYnkgY2hlY2tpbmcgaWYgbm90ICghKSBhbiBOQSBkdWUgdG8gbm90IG1lZXRpbmcgdGhlIGRhdGUgZm9ybWF0DQp0ZW1wPC0gYWlycG9ydHNbIWlzLm5hKHN0cnB0aW1lKGFpcnBvcnRzJERlcFRpbWUsIGZvcm1hdCA9ICIlSCVNIikpLF0NCg0KY2xhc3ModGVtcCREZXBUaW1lKQ0KDQojU3BsaXQgdGltZSBzZXJpZXMgZGF0YSBpbnRvIHRpbWUgaW50ZXJ2YWxzIGJ5IGV2ZXJ5IDE1IG1pbnV0ZXMgYW5kIHRoZW4gcGxvdCB0aGUgY291bnQNCm15REY8LSBtdXRhdGUodGVtcCwgYnlHcm91cCA9IGFzLm1hdHJpeChjdXQoc3RycHRpbWUoc3ViKCIoXFxkKykoXFxkezJ9KSIsICJcXDE6XFwyIiwgdGVtcCREZXBUaW1lKSwgZm9ybWF0ID0gIiVIOiVNIiksIGJyZWFrcz0iMTUgbWluIikgKSkgDQoNCiMgdW5pcXVlKG15REYkYnlHcm91cCkgI2NoZWNrIHVuaXF1ZSB2YWx1ZXMNCg0KIyBFeHRyYWN0IHRpbWUgdmFsdWUgZnJvbSB2YXJpYWJsZSBhbmQgbWFrZSBkYXRhZnJhbWVzIHVzZWQgZm9yIGdyYXBocyBhbmQgc3VtbWFyeSB0YWJsZQ0KDQpteURGWyJUaW1lIl0gPC0gTkEgIyBUaGlzIGNyZWF0ZXMgdGhlIG5ldyBjb2x1bW4gZmlsbGVkIHdpdGggIk5BIg0KbXlERiRUaW1lIDwtIHN0cmZ0aW1lKGFzLlBPU0lYbHQobXlERiRieUdyb3VwKSwgZm9ybWF0PSIlSDolTSIpDQoNCmBgYA0KIw0KIyAxLiBCaWcgRGF0YSBFeGFtcGxlDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojIE1ha2UgZGF0YWZyYW1lcyB1c2VkIGZvciBncmFwaHMgYW5kIHN1bW1hcnkgdGFibGUNCiMNCmBgYHtyfQ0KDQojIGJhc2VkIG9uIHZhcmlhYmxlIHZhbHVlcw0KZHMxIDwtIG15REZbIHdoaWNoKG15REYkRGF5b2ZNb250aCA9PSAxICYgbXlERiRPcmlnaW5TdGF0ZT09Ik5ZIiksIF0NCg0KZHMxW3dpdGgoZHMxLCBvcmRlcihUaW1lKSksIF0NCg0KYGBgDQoNCiMNCiMgUGxvdCB0aGUgZGF0YSB1c2luZyBnZ3Bsb3QyIGFuZCBwaXBlcw0KIw0KYGBge3J9DQoNCiMgUHJlcGFyZSBkYXRhc2V0IGFuZCBwaXBlIHRoZSBkYXRhc2V0IGludG8gdGhlIGdyYXBoDQpncmFwaDEgPC0gZHMxICU+JQ0KZ2dwbG90KGFlcyh4ID0gVGltZSwgeSA9IE9yaWdpbiwgZ3JvdXA9T3JpZ2luU3RhdGVOYW1lLCBjb2xvciA9IENhcnJpZXIpKSArDQogICAgICBnZW9tX3BvaW50KCkgKyAgc2NhbGVfeF9kaXNjcmV0ZShicmVha3M9YygiMDA6MDAiLCAiMTE6MDAiLCAiMTI6MDAiLCAiMTQ6MDAiLCIxNjowMCIsIjE4OjAwIiwgIjIwOjAwIiwiMjM6MDAiKSAgKSANCiAgICAgIGxhYnModGl0bGUgPSAiTmV3IFlvcmsgU3RhdGUgQWlycG9ydCBEZXBhcnR1cmVzIGJ5IFRpbWUgSW50ZXJ2YWxzIiwNCiAgICAgICAgICAgc3VidGl0bGUgPSAiKlRoZSBkYXRhIGZyYW1lIGlzIHNlbnQgdG8gdGhlIHBsb3QgdXNpbmcgcGlwZXMiLA0KICAgICAgICAgICB5ID0gIkFpcnBvcnQgT3JpZ2luIGNvZGUiLA0KICAgICAgICAgICB4ID0gIkRlcGFydHVyZSBUaW1lIikgKyB0aGVtZV9idyhiYXNlX3NpemUgPSA4KSArDQogICAgICB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KI3ByaW50b3V0IGdyYXBoICMxDQpncmFwaDENCg0KYGBgDQojIA0KIyBUdXJuIHRoZSBncGxvdCBpbnRvIGFuIGludGVyYWN0aXZlIGNoYXJ0LiANCiMNCmBgYHtyfQ0KDQpnZ3Bsb3RseShncmFwaDEpDQoNCmBgYA0KDQojIDIuIFNhbXBsaW5nIGFuZCBCSSBTdW1tYXJ5IEV4YW1wbGUNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgQ291bnQgRGlzdHJpYnV0aW9uIGJ5IFN0YXRlIGFuZCBPcmlnaW4gQ2l0eSBvZiBBaXJwb3J0IERlcGFydHVyZXMgYnkgQ2Fycmllcg0KIw0KDQoNCmBgYHtyfQ0KDQojIENyZWF0ZSBkYXRhc2V0IHdpdGggb3ZlcmFsbCBjb3VudCBkaXN0cmlidXRpb25zDQoNCmRzMiA8LSBteURGWyB3aGljaChteURGJENhcnJpZXIgJWluJSBjKCI5RSIsIkFBIiwiQVMiLCJCNiIsIkRMIiwiRVYiLCJGOSIpKSwgXQ0KDQpkZnJfcHJvcCA8LSBkczIgJT4lIA0KICBjb3VudChPcmlnaW5TdGF0ZSwgQ2FycmllcikgJT4lICAjIGdyb3VwX2J5KCkgJiBzdW1tYXJpc2Uobj1uKCkpIGFyZSBpbXBsaWNpdA0KICBtdXRhdGUocHJvcCA9IHByb3AudGFibGUobikpICAgICAjIHByb3AgPSBuL3N1bShuKSB3b3JrcyB0b28gLyAgcHJvcC50YWJsZShuKQ0KDQphcy5kYXRhLmZyYW1lKGRmcl9wcm9wKSAgICAgICAgICAgICMgcHJpbnQgZGYNCg0KYGBgDQoNCmBgYHtyfQ0KI1Bsb3QgZGlzdHJpYnV0aW9uIHBlcmNlbnRhZ2Ugc3VtbWFyeSBmb3IgbW9udGggb2YgSmFudWFyeQ0KDQpnZ19wcm9wIDwtIGdncGxvdChkYXRhID0gZGF0YS5mcmFtZSgpDQogICAgICAgICAgICAgICAgICAsIGFlcyh4ID1PcmlnaW5TdGF0ZSwgeSA9IHByb3AsIGZpbGwgPSBDYXJyaWVyKSkgKyANCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIHBvc2l0aW9uID0gJ3N0YWNrJywgYWxwaGEgPSAyLzMpICsgIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKyAgDQogIHNjYWxlX3hfZGlzY3JldGUoYnJlYWtzPWMoIkFLIiwgIkNBIiwiRkwiLCAiSUwiLCAiR0EiLCJMQSIsIk1JIiwiTU8iLCJOQyIsIk9IIiwgIk5KIiwiTlkiLCJQQSIsIlRYIiwiV1kiKSAgKSArDQogIHNjYWxlX2ZpbGxfZmV3KCdtZWRpdW0nLCBkcm9wID0gRkFMU0UpICsgICAgICAgICAgICAjIGtlZXAgbGV2ZWxzLCBpZiBkYXRhIGlzIGZpbHRlcmVkDQogIGxhYnMoeCA9ICdTdGF0ZScsIHkgPSBOVUxMLCBmaWxsID0gJ0NhcnJpZXInLA0KICAgICAgIHRpdGxlID0gJ0Rpc3RyaWJ1dGlvbiBvZiBGbGlnaHRzIGJ5IFN0YXRlIGFuZCBDYXJyaWVyIGZvciBNb250aCBvZiBKYW51YXJ5JykgICMgdGl0bGUgb2YgZ3JhcGgNCg0KZ2dfcHJvcCAlKyUgICBkZnJfcHJvcCAgDQoNCmBgYA0KDQojDQojIEVuZCBvZiBwcm9ncmFtLg0KIw0KDQoNCg==